/*
 * Copyright (C) 2012 SeekDeepWithin.com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package com.seekdeepwithin;

import com.almworks.sqlite4java.*;
import com.seekdeepwithin.Controls.*;
import com.seekdeepwithin.Data.ReadLink;
import com.seekdeepwithin.Data.ITab;
import com.seekdeepwithin.Data.Book;
import com.seekdeepwithin.DataAccess.BookSearchService;
import com.seekdeepwithin.Controls.SplitPanel;
import com.seekdeepwithin.Controls.StartControl;
import com.seekdeepwithin.Controls.ReaderControl;
import com.seekdeepwithin.Controls.IWebEventHandler;
import com.seekdeepwithin.Controls.WebControl;
import com.seekdeepwithin.DataAccess.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.*;
import javafx.concurrent.Worker.State;
import javafx.event.*;
import javafx.fxml.FXML;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.control.Dialogs.DialogOptions;
import javafx.scene.layout.*;
import javafx.stage.*;
import javafx.stage.Window;

/**
 * User: Jonathan Montiverdi Date: 9/26/12
 *
 * Main controller for seek deep within.
 */
public class MainController implements Closeable, IWebEventHandler
{
   @FXML
   private Menu _BookMenu;
   @FXML
   private TabPane _TabPane;
   @FXML
   private TabPane _TabSearch;
   @FXML
   private TextField _SearchText;
   @FXML
   private SplitPanel _MainSplitPanel;
   @FXML
   private Label _StatusLabel;
   @FXML
   private Label _JobLabel;
   @FXML
   private ProgressBar _JobProgress;
   @FXML
   private InformationBar _InfoBar;
   private StartControl m_StartControl;
   private MainProperties m_Properties;
   private static String s_TskPath;
   private static MainController s_Instance;
   private Stage m_Stage;

   public static MainController getInstance ()
   {
      if (s_Instance == null) {
         s_Instance = new MainController ();
      }
      return s_Instance;
   }

   /**
    * Initializes the main controller.
    */
   public void start (Stage primaryStage)
   {
      try {
         this.m_Stage = primaryStage;
         this.m_Properties = new MainProperties ();
         this._StatusLabel.textProperty ().bind (this.getProps ().statusTextProperty ());
         this._JobLabel.textProperty ().bind (this.getProps ().jobTextProperty ());
         this._JobProgress.progressProperty ().bind (this.getProps ().jobProgressProperty ());
         this.getProps ().setJobProgress (0);
         this.getProps ().setStatusText ("Welcome");
         this.getProps ().setJobText ("");

         SdwDatabase.getInstance ().Init ();
         this.loadBookMenu ();
         this.m_StartControl = new StartControl ();
         this.addTab (this.m_StartControl);
      } catch (SQLiteException ex) {
         Dialogs.showErrorDialog (primaryStage, "An error occured while initializes the main database.",
                                  "An error was detected with the database.", "Error", ex);
      }
   }

   /**
    * Gets the properties for this object.
    * @return Properties for this object.
    */
   public MainProperties getProps ()
   {
      return this.m_Properties;
   }

   /**
    * Gets the primary stage of the application.
    * @return The application's primary stage.
    */
   public Stage getStage ()
   {
      return this.m_Stage;
   }

   /**
    * Gets the main window.
    * @return The application's main window
    */
   public Window getMainWindow ()
   {
      return this._MainSplitPanel.getScene ().getWindow ();
   }

   /**
    * Queues the given job and starts it.
    * @param service Service implementation of the current job.
    */
   public void queueJob (Service service)
   {
      this.getProps ().jobTextProperty ().bind (service.messageProperty ());
      this.getProps ().jobProgressProperty ().bind (service.progressProperty ());

      service.stateProperty ().addListener (new ChangeListener<State> ()
      {
         @Override
         public void changed (ObservableValue<? extends State> observable, State oldValue, State newValue)
         {
            if (newValue == State.CANCELLED || newValue == State.FAILED || newValue == State.SUCCEEDED) {
               MainController.getInstance ().getProps ().jobTextProperty ().unbind ();
               MainController.getInstance ().getProps ().jobProgressProperty ().unbind ();
               new Timer ().schedule (new TimerTask ()
               {
                  @Override
                  public void run ()
                  {
                     cancel ();
                     Platform.runLater (new Runnable ()
                     {
                        @Override
                        public void run ()
                        {
                           MainController.getInstance ().getProps ().setJobText ("");
                           MainController.getInstance ().getProps ().setJobProgress (0);
                        }
                     });
                  }
               }, 2000);
            }
         }
      });
      service.reset ();
      service.start ();
   }

   /**
    * Shows the information bar.
    * @param text The text to display on the info bar.
    * @param showCloseButton True to show the close button, otherwise false.
    * @param iconType The type of icon to display on the information bar.
    * @param hideDelay A delay in seconds to wait until the information bar will
    */
   public void showInfo (String text, boolean showCloseButton, int iconType, int hideDelay)
   {
      this._InfoBar.show (text, showCloseButton, iconType, hideDelay);
   }

   /**
    * Hides the information bar.
    */
   public void hideInfo ()
   {
      this._InfoBar.hide ();
   }

   /**
    * Loads the book menu.
    */
   private void loadBookMenu ()
   {
      this._BookMenu.getItems ().clear ();
      for (final Book book : SdwDatabase.getInstance ().getBooks ()) {
         MenuItem item = new MenuItem (book.getTitle ());
         item.setOnAction (new EventHandler<ActionEvent> ()
         {
            @Override
            public void handle (ActionEvent t)
            {
               MainController.getInstance ().addTab (new ReaderControl (book));
            }
         });
         this._BookMenu.getItems ().add (item);
      }
   }

   /**
    * Method called when the book list has been updated.
    */
   public void bookListUpdated ()
   {
      SdwDatabase.getInstance ().updateBooks ();
      this.loadBookMenu ();
   }

   /**
    * Gets if the TSK is available.
    * @return True if the TSK is available, otherwise false.
    */
   public static boolean hasTsk ()
   {
      return !s_TskPath.isEmpty ();
   }

   /**
    * Gets the path to the TSK database.
    * @return The path to the TSK database.
    */
   public static String getTskPath ()
   {
      return s_TskPath;
   }

   /**
    * Adds a tab to the the main tab control.
    * @param title Title of tab.
    * @param content Content to add to tab.
    */
   public synchronized void addTab (final ITab content)
   {
      final Tab tab = new Tab ();
      tab.setContent ((Node) content);
      tab.textProperty ().bind (content.tabTitleProperty ());
      tab.tooltipProperty ().bind (content.tabToolTipProperty ());
      tab.setOnClosed (new EventHandler<Event> ()
      {
         @Override
         public void handle (Event t)
         {
            content.closing ();
         }
      });
      this._TabPane.getTabs ().add (tab);
      this._TabPane.getSelectionModel ().select (tab);
   }

   /**
    * Adds the treasury of scripture knowledge to the application.
    *
    * @param path Path to database.
    */
   public void AddTsk (String path)
   {
      s_TskPath = path;
   }

   /**
    * Opens the given book.
    * @param book Book to open.
    */
   private void OpenBook (Book book)
   {
      this.addTab (new ReaderControl (book));
   }

   /**
    * Shows the start page.
    * @param event Event data.
    */
   @FXML
   private void onShowStartPage (ActionEvent event)
   {
      boolean found = false;
      for (Iterator<Tab> it = this._TabPane.getTabs ().iterator (); it.hasNext ();) {
         Tab tab = it.next ();
         if (tab.getContent () == this.m_StartControl) {
            found = true;
            this._TabPane.getSelectionModel ().select (tab);
            break;
         }
      }
      if (!found) {
         this.addTab (this.m_StartControl);
      }
   }

   /**
    * Performs a quick search.
    * @param event Event data.
    */
   @FXML
   private void onQuickSearch (ActionEvent event)
   {
      String searchText = this._SearchText.getText ();
      if (searchText.isEmpty ()) {
         return;
      }
      final WebControl control = new WebControl ();
      final Tab tab = new Tab (searchText);
      tab.setContent ((Node) control);
      this._TabSearch.getTabs ().add (tab);
      this._TabSearch.getSelectionModel ().select (tab);

      control.setHandler (this);
      control.setHtml ("<h3>Searching...</h3>");

      BookSearchService service = new BookSearchService ();
      service.setSearchText (searchText);
      service.setOnSucceeded (new EventHandler<WorkerStateEvent> ()
      {
         @Override
         public void handle (WorkerStateEvent event)
         {
            BookSearchService searchService = (BookSearchService) event.getSource ();
            control.setHtml (searchService.getValue ());
         }
      });
      service.start ();
   }

   /**
    * Opens an internal wiki link.
    * @param link ReadLink to open.
    */
   @Override
   public void openLink (String link)
   {
      this.openLink (new ReadLink (link));
   }

   /**
    * Opens an internal link.
    * @param link ReadLink to open.
    */
   public void openLink (ReadLink link)
   {
      if (link.hasBook ()) {
         Book book = SdwDatabase.getInstance ().getBook (link.getBook ());
         if (book == null) {
            if (false/*Check if the book is available*/) {
               // TODO: Ask to download book...
            } else {
               Dialogs.showErrorDialog (getStage (), "Sorry, but the book you are looking for has not been added to Seek Deep Within as of yet.",
                                        "That book is not yet available!", "Link not Found");
            }
         } else {
            ReaderControl reader = new ReaderControl (book);
            this.addTab (reader);
            reader.read (link);
         }
      }
   }

   /**
    * Opens an external wiki link.
    * @param link ReadLink to open.
    */
   @Override
   public void openExternalLink (String link)
   {
      try {
         java.awt.Desktop.getDesktop ().browse (new URI (link));
      } catch (URISyntaxException | IOException ex) {
         Dialogs.showErrorDialog (getStage (), "An error occured while starting the web browser.",
                                  "Web browser error detected!", "Error", ex);
      }
   }

   /**
    * Places the data with the given key on the clipboard.
    *
    * @param subBook Sub book data is in.
    * @param chapter Chapter data is in.
    * @param verse Verse number of the data being copied.
    */
   @Override
   public void copy (String subBook, String chapter, String verse)
   {
   }

   /**
    * Toggles the visibility of the search panel.
    * @param event Event data.
    */
   @FXML
   private void toggleShowSearch (ActionEvent event)
   {
      if (this._MainSplitPanel.getIsFirstNodeVisible ()) {
         this._MainSplitPanel.setIsFirstNodeVisible (false);
      } else {
         this._MainSplitPanel.setIsFirstNodeVisible (true);
      }
   }

   /**
    * Shows the about dialog.
    * @param event Event data.
    */
   @FXML
   private void onShowAbout (ActionEvent event)
   {
      VBox panel = new VBox (10);
      panel.setAlignment (Pos.CENTER);
      panel.getChildren ().add (LabelBuilder.create ().text ("Seek Deep Within is an application that aids in "
                                                             + "studying spiritial texts from all over the world.\nThis is the local application "
                                                             + "verion of the website below.").build ());
      panel.getChildren ().add (HyperlinkBuilder.create ().text ("Visit website")
              .onAction (new EventHandler<ActionEvent> ()
      {
         @Override
         public void handle (ActionEvent t)
         {
            openExternalLink ("http://seekdeepwithin.com/");
         }
      }).build ());
      panel.getChildren ().add (LabelBuilder.create ().text ("Version 1.0").build ());

      Dialogs.showCustomDialog (getStage (), panel, "Seek Deep Within", "About", DialogOptions.OK, null);
   }

   /**
    * Handles the close application command.
    * @param event Event data.
    */
   @FXML
   private void handleClose (ActionEvent event)
   {
      System.exit (0);
   }

   /**
    * Closes all resources.
    * @throws IOException
    */
   @Override
   public void close () throws IOException
   {
      this._InfoBar.close ();
      SdwDatabase.getInstance ().close ();
   }
}
